/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/cdev.h>

#include <alga/rng_mng.h>
#include <alga/timing.h>
#include <alga/pixel_fmts.h>
#include <alga/amd/dce4/dce4.h>

#include "regs.h"

#include "ih.h"
#include "ba.h"
#include "ucode.h"
#include "cp.h"
#include "drv.h"

#define RLC_DWS	768
#define PFP_DWS	1120
#define ME_DWS	1376

void ucode_release(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	dd = pci_get_drvdata(dev);

	release_firmware(dd->ucode.pfp);
	release_firmware(dd->ucode.me);
	release_firmware(dd->ucode.rlc);
}

int ucode_load(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	const char *chip_name;
	const char *rlc_chip_name;
	size_t pfp_req_sz;
	size_t me_req_sz;
	size_t rlc_req_sz;
	char fw_name[30];
	int err;

	chip_name = NULL;
	rlc_chip_name = NULL;

	dd = pci_get_drvdata(dev);

	switch (dd->family) {
	case CEDAR:
		chip_name = "CEDAR";
		rlc_chip_name = "CEDAR";
		break;
	case REDWOOD:
		chip_name = "REDWOOD";
		rlc_chip_name = "REDWOOD";
		break;
	case JUNIPER:
		chip_name = "JUNIPER";
		rlc_chip_name = "JUNIPER";
		break;
	case CYPRESS:
	case HEMLOCK:
		chip_name = "CYPRESS";
		rlc_chip_name = "CYPRESS";
		break;
	}

	pfp_req_sz = PFP_DWS * 4;
	me_req_sz = ME_DWS * 4;
	rlc_req_sz = RLC_DWS * 4;

	dev_info(&dev->dev, "loading %s ucode\n", chip_name);

	dd = pci_get_drvdata(dev);

	dd->ucode.pfp = NULL;
	dd->ucode.me = NULL;
	dd->ucode.rlc = NULL;

	snprintf(fw_name, sizeof(fw_name), "radeon/%s_pfp.bin", chip_name);
	err = request_firmware(&dd->ucode.pfp, fw_name, &dev->dev);
	if (err) {
		dev_err(&dev->dev, "unable to load pfp ucode:%s\n", fw_name);
		goto out;
	}
	if (dd->ucode.pfp->size != pfp_req_sz) {
		dev_err(&dev->dev, "bogus length %zu in pfp firmware \"%s\"\n",
						dd->ucode.pfp->size, fw_name);
		err = -EINVAL;
		goto out;
	}

	snprintf(fw_name, sizeof(fw_name), "radeon/%s_me.bin", chip_name);
	err = request_firmware(&dd->ucode.me, fw_name, &dev->dev);
	if (err) {
		dev_err(&dev->dev, "unable to load me ucode:%s\n", fw_name);
		goto out;
	}
	if (dd->ucode.me->size != me_req_sz) {
		dev_err(&dev->dev, "bogus length %zu in me firmware \"%s\"\n",
						dd->ucode.me->size, fw_name);
		err = -EINVAL;
		goto out;
	}

	snprintf(fw_name, sizeof(fw_name), "radeon/%s_rlc.bin", rlc_chip_name);
	err = request_firmware(&dd->ucode.rlc, fw_name, &dev->dev);
	if (err) {
		dev_err(&dev->dev, "unable to load rlc ucode:%s\n", fw_name);
		goto out;
	}
	if (dd->ucode.rlc->size != rlc_req_sz) {
		dev_err(&dev->dev, "bogus length %zu in rlc firmware \"%s\"\n",
						dd->ucode.rlc->size, fw_name);
		err = -EINVAL;
	}

out:
	if (err) {
		release_firmware(dd->ucode.pfp);
		dd->ucode.pfp = NULL;
		release_firmware(dd->ucode.me);
		dd->ucode.me = NULL;
		release_firmware(dd->ucode.rlc);
		dd->ucode.rlc = NULL;
	}
	return err;
}

void ucode_rlc_program(struct pci_dev *dev)
{
	u32 i;
	const __be32 *fw_data;
	struct dev_drv_data *dd;

	dd = pci_get_drvdata(dev);

	wr32(dev, 0, RLC_CTL);

	wr32(dev, 0, RLC_HB_BASE);
	wr32(dev, 0, RLC_HB_CTL);
	wr32(dev, 0, RLC_HB_RPTR);
	wr32(dev, 0, RLC_HB_WPTR);
	wr32(dev, 0, RLC_HB_WPTR_LSB_ADDR);
	wr32(dev, 0, RLC_HB_WPTR_MSB_ADDR);
	wr32(dev, 0, RLC_MC_CTL);
	wr32(dev, 0, RLC_UCODE_CTL);

	fw_data = (const __be32 *)dd->ucode.rlc->data;
	for (i = 0; i < RLC_DWS; ++i) {
		wr32(dev, i, RLC_UCODE_ADDR);
		wr32(dev, be32_to_cpup(fw_data++), RLC_UCODE_DATA); 
	}
	wr32(dev, 0, RLC_UCODE_ADDR);

	wr32(dev, RLC_ENA, RLC_CTL);
}

void ucode_cp_program(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	u32 cp_rb_ctl;
	const __be32 *fw_data;
	int i;

	dd = pci_get_drvdata(dev);

	/* XXX: rb size values for ucode programming */
	cp_rb_ctl = RB_NO_UPDATE | SET(RB_BLK_LOG2_QWS, 15)
						| SET(RB_BUF_LOG2_QWS, 3);
#ifdef __BIG_ENDIAN
	cp_rb_ctl |= BUF_SWAP_32BIT;
#endif
	wr32(dev, cp_rb_ctl, CP_RB_CTL);

	fw_data = (const __be32 *)dd->ucode.pfp->data;
	wr32(dev, 0,CP_PFP_UCODE_ADDR);
	for (i = 0; i < PFP_DWS; ++i)
		wr32(dev, be32_to_cpup(fw_data++), CP_PFP_UCODE_DATA);
	wr32(dev, 0, CP_PFP_UCODE_ADDR);

	fw_data = (const __be32 *)dd->ucode.me->data;
	wr32(dev, 0, CP_ME_RAM_WADDR);
	for (i = 0; i < ME_DWS; ++i)
		wr32(dev, be32_to_cpup(fw_data++), CP_ME_RAM_DATA);
	wr32(dev, 0, CP_PFP_UCODE_ADDR); 
	wr32(dev, 0, CP_ME_RAM_WADDR);
	wr32(dev, 0, CP_ME_RAM_RADDR);
}
